"""
需求分析：
聊天工具是CS程序，C是每一个客户端，S是服务器端。

服务器应该具有的功能：
1.TCP服务，写成类的形式。
2.启动服务，包括绑定地址和端口，监听。
3.建立连接，能够和多个客户端建立连接。
4.分发，将接受的某个用户的信息转发到已经连接的所有客户端。
5.停止服务。
6.记录连接的客户端。
"""

import socket
import threading
import logging

logging.basicConfig(level=logging.INFO)

class ChatProcess:
    # init方法增加生成socket实例，ip+端口对。
    def __init__(self, ip='127.0.0.1', port=9999):
        self.sock = socket.socket()
        self.addr = (ip, port)

        self.event = threading.Event()

        self.client_ipaddrs = {}

    # start方法增加socket实例绑定和监听。
    def start(self):
        self.sock.bind(self.addr)
        self.sock.listen()
        # 增加启动_accept方法
        threading.Thread(target=self._accept, name='accept').start()

    def _accept(self):
        while not self.event.is_set():
            conn, client = self.sock.accept()

            self.client_ipaddrs[client] = conn

            threading.Thread(target=self._recv, args=(conn, client), name='recv').start()

    # 服务器通知，退出机制
    def stop(self):
        for i in self.client_ipaddrs.values():
            i.close()

        self.sock.close()

        self.event.wait(5)  # 清理需要花点时间
        self.event.set()

    def _recv(self, conn, client):
        while not self.event.is_set():
            # 异常处理，防止客户端上点断开连接
            try:
                data = conn.recv(1024).decode()
            except Exception as e:
                logging.info(e)
                data = "quit"  # 如果抛异常，直接强制退出

            logging.info(data)

            # 客户端通知，退出机制
            if data == "quit":
                self.client_ipaddrs.pop(client)
                conn.close()
                break  # break会跳出循环，即这个线程结束

            msg = "ack {}".format(data)
            for i in self.client_ipaddrs.values():
                i.send(msg.encode())


cs = ChatProcess()
cs.start()

# 查看服务端线程状态
e = threading.Event()

def watch():
    while not e.wait(3):
        logging.info(threading.enumerate())

threading.Thread(target=watch, daemon=True).start()

# 服务器端通知
while True:
    cmd = input(">>>").strip()
    if cmd == "quit":
        cs.stop()
        break